package controller;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.TreeSet;
import java.util.zip.DataFormatException;

import model.Account;
import model.AccountException;
import model.LivroCaixaException;
import model.LoginException;
import model.User;

/**
 * Caixa que gerencia o dinheiro e opera��es no sistema
 * 
 * @author Matheus
 * @author Tiago Lucas
 * 
 */

public class Cashier {

	private static Cashier myself;

	private User user;
	private Account account;

	private boolean loggedin = false;

	private AccountManager accountmanager;
	private CommitmentManager commitmentmanager;

	private UserManager usermanager;
	private MailManager mailmanager;

	private ReportManager actualReport;
	
	private static final String EMAIL_REGEX = ".+\\@[a-z]+[a-z0-9]*(\\.[a-z]+[a-z0-9]*)+";
	private static final String NUMBER_REGEX = "[0-9]+(\\-[0-9])?";
	private static final String PASSWORD_REGEX = "[a-zA-Z0-9]+[\\S]*";
	private static final String LOGIN_REGEX = "[a-zA-Z]+[[\\S][a-zA-Z0-9]+]*";

	/**
	 * Construtor privado da classe Cashier
	 */
	private Cashier() {

		// O usu�rio precisa se logar para usar
		this.user = null;
		this.account = null;
		
		this.usermanager = UserManager.getInstance();
		//this.mailmanager = MailManager.getInstance();
		
		this.accountmanager = null;
		this.commitmentmanager = null;

	}

	/**
	 * Limpa todos os dados j� existentes no sistema (logoff)
	 */
	public void cleanAll() {
		
		// O usu�rio precisa se logar para usar
		this.user = null;
		this.account = null;
		this.loggedin = false;

		this.usermanager.cleanAll();

		this.accountmanager = null;
		this.commitmentmanager = null;

	}

	public ReportManager report(){
		return this.actualReport;
	}
	
	/**
	 * Retorna uma inst�ncia do tipo Cashier (padr�o Singleton)
	 * @return Cashier
	 */
	public static Cashier getInstance() {
		return myself;
	}

	static {
		myself = new Cashier();
	}

	/**
	 * Registra um usuario no sistema com login unico, podendo nao informar apenas o e-mail (demais campos sao obrigatorios)
	 * @param login login do usu�rio
	 * @param password senha do usu�rio
	 * @param name nome do usu�rio
	 * @param gender sexo do usu�rio
	 * @param email email do usu�rio
	 * @return True, em caso de sucesso, false, caso contr�rio
	 * @throws LoginException
	 */
	public boolean register(String login, String password, String name,
			String gender, String email) throws LoginException {

		// Esqueceu de digitar login ou senha no registro
		if (login.equals("") || password.equals("") || name.equals("")) {
			throw new LoginException("Missing Argument");
		}

		// Checar se o login e a senha s� t�m alfanum�ricos, h�fen e underline.
		// E se tem m�nimo de 4 caracteres
		if ((passwordIsAcceptable(password))) {
			// Tudo Ok
		} else {
			throw new LoginException("Senha inadequada : certifique-se de que nao usou espacos em branco e que ela comeca com uma letra ou um numero.");
		}
		
		if ((loginIsAcceptable(login))) {
			// Tudo Ok
		} else {
			throw new LoginException("Login inadequado : certifique-se de que nao usou espacos em branco e que ele comeca com uma letra.");
		}

		  // Sexo tem que ser male ou female
		if(gender != null){
			if (gender.length() > 0){
				if ((gender.equalsIgnoreCase("M")) || (gender.equalsIgnoreCase("F"))) {
					gender = gender.toUpperCase(); }
				  else {
					  throw new LoginException("Invalid Argument");
				  }
			}	
		}
		
		
		 

		// Agora checar se o login j� existe
		if (usermanager.loginExists(login)) {
			throw new LoginException("Login already registered");
		}

		// Se tem email, saber se ele � bem formado
		if ((email != null) && (!email.equals(""))) {
			if (!emailIsAcceptable(email)) {
				throw new LoginException("Invalid e-mail");
			}
		}

		User newuser = new User(login, password, name, email, gender);

		return usermanager.register(newuser);
	}

	public TreeSet<Account> getAccounts(){
		if(accountmanager == null){
			
			return new TreeSet<Account>();
		}
		
		return accountmanager.getAccounts();
	}
	/**
	 * Retorna o usuario logado
	 * @return Usu�rio
	 */
	public User getUser() {

		if (loggedin) {
			return user;
		} else {
			return null;
		}
	}

	// Retorna detalhes do usu�rio atual
	/**
	 * Retorna o nome do usu�rio atualmente logado
	 * @return Nome do usu�rio
	 */
	public String getUserName() {
		return this.user.getName();
	}

	/**
	 * Retorna o nome do usu�rio pelo login
	 * @param login login do usu�rio
	 * @return Nome do usu�rio
	 */
	public String getUserName(String login) {
		return this.usermanager.findUser(login).getName();
	}

	/**
	 * Retorna o sexo do usu�rio atualmente logado
	 * @return Sexo do usu�rio
	 */
	public String getUserGender() {
		return this.user.getSex();
	}
	
	/**
	 * Retorna o sexo do usu�rio pelo login
	 * @param login login do usu�rio
	 * @return Sexo do usu�rio
	 */
	public String getUserGender(String login) {
		return this.usermanager.findUser(login).getSex();
	}

	/**
	 * Retorna o email do usu�rio atualmente logado
	 * @return email do usu�rio
	 */
	public String getUserEmail() {
		return this.user.getEmail();
	}

	/**
	 * Retorna o email do usu�rio pelo login
	 * @param login login do usu�rio
	 * @return email do usu�rio
	 */
	public String getUserEmail(String login) {
		return this.usermanager.findUser(login).getEmail();
	}

	/*
	 * M�todos de contas de usu�rio
	 */

	// cria��o de conta nova para usu�rio
	
	/**
	 * Cria nova conta ligada a um dado usuario que esta logado retornando o identificador daquela conta. Recebe login, r�tulo da conta, ag�ncia e conta.
	 * @param login login do usu�rio
	 * @param label r�tulo da conta
	 * @param agency ag�ncia
	 * @param account n�mero da conta
	 * @return Identificador da conta
	 * @throws AccountException
	 * @throws LoginException
	 * 
	 */
	public String createAccount(String login, String label, String agency,
			String account) throws AccountException, LoginException {

		// Esqueceu de digitar conta, ag�ncia ou r�tulo
		if (label == null || agency == null || account == null
				|| agency == null) {
			throw new AccountException("Missing Argument");
		}

		// Esqueceu de digitar conta, ag�ncia ou r�tulo
		if (label.equals("") || agency.equals("") || account.equals("")
				|| agency.equals("")) {
			throw new AccountException("Missing Argument");
		}

		// Se o usu�rio n�o existe
		if (!usermanager.loginExists(login)) {
			throw new LoginException("Login unregistered");
		}

		// Esqueceu de digitar conta, ag�ncia ou r�tulo
		if ((label.length() == 0) || (agency.length() == 0)
				|| (account.length() == 0)) {
			throw new AccountException("Missing Argument");
		}

		// Testando a r�tulo
		if (!labelIsAcceptable(label)) {
			throw new AccountException("Invalid Argument");
		}

		// Testando conta e ag�ncia
		if ((numberIsAcceptable(agency)) && (numberIsAcceptable(account))) {
			// Tudo Ok
		} else {
			throw new AccountException("Invalid Argument");
		}

		// // Testar se esta conta j� existe
		if (accountmanager.accountExists(label, agency, account)) {
			throw new AccountException("Account Already Exists");
		}

		return accountmanager.createAccount(label, agency, account);
	}

	/**
	 * Realiza login de usu�rio
	 * 
	 * @param login login do usu�rio
	 * @param password senha do usu�rio
	 * @return o usu�rio que acaba de se logar
	 * @throws LoginException
	 */
	
	public User doLogin(String login, String password) throws LoginException {

		// Checar se o login e a senha s� t�m alfanum�ricos, h�fen e underline.
		// E se tem m�nimo de 4 caracteres
		if ((loginIsAcceptable(login)) && (passwordIsAcceptable(password))) {
			// Tudo Ok
		} else {

			throw new LoginException("Invalid Argument");
		}

		// Agora checar se o login n�o existe
		if (!usermanager.loginExists(login)) {

			throw new LoginException("Login unregistered");
		} else { // Login existe, ver se a senha est� correta

			if (loggedin) {
				throw new LoginException("AlreadyLoggeddIn");
			}
			User user = usermanager.findUser(login);

			if (user.getPassword().compareTo(password) == 0) {
				// Senha est� ok
				this.user = user;
				this.loggedin = true;

				this.accountmanager = user.getAccountManager();
				this.commitmentmanager = user.getCommitmentManager();

				return user;
			} else { // Senha errada
				throw new LoginException("Wrong password");
			}
		}
	}
	
	/**
	 * Faz logoff no sistema caso o login informado esta logado
	 * @param login login do usu�rio
	 * @param password senha do usu�rio
	 * @return True, caso sucesso, False caso contrario
	 * @throws LoginException
	 */
	public boolean doLogoff(String login, String password)
			throws LoginException {

		if (!loggedin) {
			throw new LoginException("Not Logged In");
		} else {
			this.user = null;
			this.account = null;
			this.loggedin = false;

			this.accountmanager = null;
			this.commitmentmanager = null;
			
			return true;
		}
	}

	/**
	 * Verifica se o usu�rio dono deste login esta logado
	 * @param login login do usu�rio
	 * @return True caso o usu�rio esteja logado, false, caso contr�rio
	 */
	public boolean isLogged(String login) {
		if ((loggedin) && (user.getLogin().compareTo(login) == 0)) {
			return true;
		} else {
			return false;
		}
	}

	/*
	 * M�todos sobre Contas
	 */
	
	/**
	 * Retorna o saldo da conta padr�o
	 * @return Saldo da conta padr�o
	 */
	public double getDefAccOverallAmount() {

		return accountmanager.getDefAccOverallAmount();
	}

	/**
	 * Retorna o identificador da conta padr�o
	 * @return Identificador da conta
	 */
	public String getDefaultAccountId() {
		return accountmanager.getDefaultAccountId();
	}

	/**
	 * Retorna o saldo de uma conta espec�fica
	 * @param accId identificador da conta
	 * @return Saldo da conta
	 * @throws AccountException
	 */
	public double getAccOverallAmount(String accId) throws AccountException {

		return accountmanager.getAccOverallAmount(accId);
	}

	/**
	 * Adiciona uma opera��o na conta padr�o e retorna o ID da opera��o
	 * @param type tipo da opera��o
	 * @param way m�todo da opera��o
	 * @param amount quantia da opera��o
	 * @return Identificador daquela opera��o
	 * @throws AccountException
	 * @throws ParseException 
	 */
	public String addOperationIntoDefaultAccount(String type, String way,
			BigDecimal amount, String description, String date) throws AccountException, ParseException {

		return accountmanager
				.addOperationIntoDefaultAccount(type, way, amount, description, DateFormat.getDateInstance().parse(date));
	}

	/**
	 * Adiciona opera��o em conta e retorna o ID da opera��o
	 * @param accId identificador da conta
	 * @param type tipo da opera��o
	 * @param way m�todo da opera��o
	 * @param amount quantia da opera��o
	 * @return Identificador daquela opera��o
	 * @throws AccountException
	 * @throws ParseException 
	 */
	public String addOperation(String accId, String type, String way,
			BigDecimal amount, String description, String date) throws AccountException, ParseException {

		return accountmanager.addOperation(accId, type, way, amount, description, DateFormat.getDateInstance().parse(date));
	}

	/**
	 * Remove opera��o em conta
	 * @param opId identificador da opera��o
	 * @return true, caso a opera��o tenha sido removida com sucesso, false, caso contr�rio
	 * @throws AccountException
	 */
	public boolean removeOperation(String opId) throws AccountException {
		return accountmanager.removeOperation(opId);
	}

	/**
	 * Remove conta
	 * @param accId identificador da conta
	 * @return true, caso a conta tenha sido removida com suceso, false, caso contr�rio
	 * @throws AccountException
	 */
	public boolean removeAccount(String accId) throws AccountException {
		return accountmanager.removeAccount(accId);
	}

	/**
	 * Retorna tipo de opera��o
	 * @param opId identificador da opera��o
	 * @return String
	 * @throws AccountException
	 */
	public String getOperationType(String opId) throws AccountException {
		return accountmanager.getOperationType(opId);
	}

	/**
	 * Retorna m�todo de opera��o
	 * @param opId identificador da opera��o
	 * @return M�todo da opera��o
	 * @throws AccountException
	 */
	public String getOperationWay(String opId) throws AccountException {
		return accountmanager.getOperationWay(opId);
	}

	/**
	 * Retorna quantia da opera��o
	 * @param opId identificador da opera��o
	 * @return Quantia da opera��o
	 * @throws AccountException
	 */
	public BigDecimal getOperationAmount(String opId) throws AccountException {
		return accountmanager.getOperationAmount(opId);
	}

	/**
	 * Retorna quantidade de opera��es totais
	 * @return A quantidade de opera��es
	 */
	public int getNumberOfOperationsReport() {
		return accountmanager.getNumberOfOperationsReport();
	}

	/**
	 * Retorna quantidade de opera��es de uma conta
	 * @param accId identificador da conta
	 * @return A quantidade de opera��es
	 */
	public int getNumberOfOperationsReport(String accId) {
		return this.accountmanager.getNumberOfOperationsReport(accId);
	}

	/**
	 * Retorna o n�mero de opera��es de cr�dito de uma conta
	 * @param accId identificador da conta
	 * @return A quantidade de opera��es de cr�dito
	 */
	public int getNumberOfCreditOperationsReport(String accId) {
		return this.accountmanager.getNumberOfCreditOperationsReport(accId);
	}

	/**
	 * Retorna o n�mero de opera��es de cr�dito em todas as contas
	 * @return A quantidade de opera��es de cr�dito
	 */
	public int getNumberOfCreditOperationsReport() {
		return this.accountmanager.getNumberOfCreditOperationsReport();
	}

	/**
	 * Retorna o n�mero de opera��es de d�bito de uma conta
	 * @param accId identificador da conta
	 * @return A quantidade de opera��es de d�bito
	 */
	public int getNumberOfDebtOperationsReport(String accId) {
		return this.accountmanager.getNumberOfDebtOperationsReport(accId);
	}

	/**
	 * Retorna o n�mero de opera��es de d�bito em todas as contas
	 * @return A quantidade de opera��es de d�bito
	 */
	public int getNumberOfDebtOperationsReport() {
		return this.accountmanager.getNumberOfCreditOperationsReport();
	}

	/**
	 * Gera relat�rio com informa��es a respeito das opera��es e compromissos realizados em uma conta
	 * @param begin data in�cio
	 * @param end data fim
	 * @param operationType tipo da opera��o
	 * @param idAccount identificador da conta
	 * @throws AccountException
	 */
	public void generateReport(String begin, String end,
			String operationType, String idAccount) throws AccountException {
		Date beginDate = null;
		Date endDate = null;
		if (begin != null) {
			if (!begin.equals("")) {
				try {
					beginDate = DateFormat.getDateInstance().parse(begin);
				} catch (ParseException e) {
					e.printStackTrace();
				}
			}
		}
		if (end != null) {
			if (!end.equals("")) {
				try {
					endDate = DateFormat.getDateInstance().parse(end);
				} catch (ParseException e) {
					e.printStackTrace();
				}
			}
		}

		this.actualReport = this.accountmanager.generateReport(beginDate, endDate,
					operationType, idAccount);
		if(this.actualReport == null){
			this.actualReport = new ReportManager(this.user,null,null,null,idAccount);
		}
		this.actualReport.addAllCommitments(commitmentmanager.getAllCommitments());
	}

	/**
	 * Adiciona um compromisso a uma conta
	 * @param label r�tulo do compromisso
	 * @param dateString data do compromisso
	 * @param amount quantia do compromisso
	 * @param type tipo do compromisso
	 * @param frequency frequ�ncia do compromisso
	 * @return True se foi adicionado com sucesso, false caso contr�rio
	 * @throws ParseException
	 */
	public String addCommitment(String label, String dateString,
			BigDecimal amount, String type, String frequency, String description)
			throws ParseException {
		Date date = DateFormat.getDateInstance().parse(dateString);
		return commitmentmanager.addCommitment(label, date, amount, type,
				frequency, description);
	}

	/**
	 * Adiciona um compromisso a uma conta
	 * @param login login do usu�rio
	 * @param label r�tulo do compromisso
	 * @param dateString data do compromisso
	 * @param amount quantia do compromisso
	 * @param type tipo do compromisso
	 * @param frequency frequ�ncia do compromisso
	 * @return O ID do compromisso
	 */
	public String addCommitment(String login, String label, String dateString,
			BigDecimal amount, String type, String frequency, String description) {
		User user = this.usermanager.findUser(login);
		try {
			Date date = DateFormat.getDateInstance().parse(dateString);
			return commitmentmanager.addCommitment(label, date, amount, type,
					frequency, description);
		} catch (ParseException e) {
			return "erro";
		}
	}

	/**
	 * Remove um compromisso
	 * @param commId identificador do compromisso
	 * @return True se foi removido com sucesso, false caso contr�rio
	 * @throws AccountException
	 */
	public boolean removeCommitment(String commId) throws AccountException {

		return commitmentmanager.removeCommitment(commId);
	}

	/**
	 * Retorna o numero de compromissos
	 * @return A quantidade de compromissos
	 */
	public int numberOfCommitments() {
		return commitmentmanager.getNumberOfCommitmentsReport();
	}

	/**
	 * Retorna o r�tulo do compromisso registrado
	 * @param id identificador do compromisso
	 * @return R�tulo deste compromisso
	 * @throws AccountException
	 */
	public String getCommitmentLabel(String id) throws AccountException {
		return commitmentmanager.getCommitmentLabel(id);
	}

	/**
	 * Retorna a data do compromisso
	 * @param commId identificador do compromisso
	 * @return Data deste compromisso
	 * @throws AccountException
	 */
	public String getCommitmentDate(String commId) throws AccountException {

		return commitmentmanager.getCommitmentDate(commId).toLocaleString()
				.split(" ")[0];
	}

	/**
	 * Retorna o valor envolvido naquele compromisso
	 * @param commId identificador do compromisso
	 * @return Valor deste compromisso
	 * @throws AccountException
	 */
	public int getCommitmentAmount(String commId) throws AccountException {

		return commitmentmanager.getCommitmentAmount(commId).intValue();
	}

	/**
	 * Retorna o tipo do compromisso
	 * @param commId identificador do compromisso
	 * @return String tipo do compromisso
	 * @throws AccountException
	 */
	public String getCommitmentType(String commId) throws AccountException {
		return commitmentmanager.getCommitmentType(commId);
	}

	/**
	 * Retorna a frequencia daquele compromisso
	 * @param commId identificador do compromisso
	 * @return Frequ�ncia deste compromisso
	 * @throws AccountException
	 */
	public String getCommitmentFrequency(String commId) throws AccountException {
		return commitmentmanager.getCommitmentFrequency(commId);
	}

	/**
	 * Retorna o numero de compromissos
	 * @return n�mero de compromissos
	 */
	public int getNumberOfCommitmentsReport() {
		return this.commitmentmanager.getNumberOfCommitmentsReport();
	}

	/**
	 * Retora o conte�do de um arquivo de entrada de dados banc�rios
	 * @param filePath caminho para arquivo
	 * @return String
	 * @throws LivroCaixaException
	 */
	public String getFileContent(String filePath) throws LivroCaixaException {

		return FileInManager.getFileContent(filePath);
	}

	/**
	 * Submete um conjunto de opera��es banc�rias retornados pelo m�todo getFileContent
	 * @param fileContent
	 * @throws LivroCaixaException
	 * @throws ParseException 
	 */
	public void submitBankOperations(String fileContent, String accountID)
			throws LivroCaixaException, ParseException {

		String[] lines = fileContent
				.split(FileInManager.BREAK_LINE_IN_FILE_IN_CHARACTER);

		String[] parameters = null;
		for (int i = 0; i < lines.length; i++) {
			parameters = lines[i]
					.split(FileInManager.BREAK_PARAMENTER_EXPRESSION);

			if (parameters.length != 5) {
				throw new LivroCaixaException(
						"Erro na formatacao do arquivo de entrada de operacoes : abortar entrada de dados");
			}

			if (parameters[0].trim().equalsIgnoreCase("type")) {
				continue;
			}
			try {

				accountmanager.addOperation(accountID, parameters[0]
						.trim(), parameters[1].trim(), new BigDecimal(
						parameters[2].trim()), parameters[3].trim(), DateFormat.getDateInstance().parse(parameters[4].trim()));

			} catch (NumberFormatException e) {
				throw new LivroCaixaException(
						"Erro na formatacao do arquivo de entrada de operacoes (quarto parametro deve ser um numero) : abortar entrada de dados");
			}
		}
	}

	public Account getAccount(String id){
		return this.accountmanager.getAccount(id);
	}
	/**
	 * Remove um usu�rio
	 * @param login logihahan do usu�rio
	 * @return True se a remo��o foi bem sucedida, false caso contr�rio
	 * @throws LoginException
	 */
	public boolean removeUser(String login) throws LoginException {
		User user = usermanager.findUser(login);

		if (user == null) {
			throw new LoginException("Login unregistered");
		}
		
		if (!loggedin) {
			throw new LoginException("Not Logged In");

		}
		
		if (! this.usermanager.loginExists(login)) {
			throw new LoginException("Login unregistered");
		}

		return usermanager.removeUser(user);
		
	}

	/**
	 * Testa se o r�tulo � adequado �s nossas restri��es de cadeia de caracteres
	 * @param label r�tulo a ser testado
	 * @return True se for aceit�vel, false caso contr�rio
	 */
	private boolean labelIsAcceptable(String label) {
		if (label.length() < 4) {
			return false;
		}

		return true;
	}

	/**
	 * Testa se o login � adequado �s nossas restri��es de cadeia de caracteres, m�nimo de 4 caracteres alfanum�ricos e ".", "-" e "_"
	 * @param login login a ser testado
	 * @return True se for aceit�vel, false caso contr�rio
	 */
	private boolean loginIsAcceptable(String string) {

	// Tamanho m�nimo do login de 4 caracteres
	if (string.length() < 4) {
			return false;
		}

		if (!string.matches(LOGIN_REGEX)) {
			return false;
		}

		return true;
	}

	/**
	 * Testa se a senha � adequada �s nossas restri��es de cadeia de caracteres, m�nimo de 4 caracteres alfanum�ricos e ".", "-" e "_"
	 * @param senha senha a ser testada
	 * @return True se for aceit�vel, false caso contr�rio
	 */
	private boolean passwordIsAcceptable(String string) {

		// Tamanho m�nimo da senha de 4 caracteres
		if (string.length() < 4) {
			return false;
		}

		if (!string.matches(PASSWORD_REGEX)) {
			return false;
		}

		return true;
	}

	/**
	 * Testa se o email � adequado �s nossas restri��es de cadeia de caracteres
	 * @param email email a ser testado
	 * @return True se for aceit�vel, false caso contr�rio
	 */
	private boolean emailIsAcceptable(String email) {
		if (!email.matches(EMAIL_REGEX)) {
			return false;
		}

		return true;
	}

	/**
	 * Testa se o n�mero para conta ou ag�ncia � adequado �s nossas restri��es de cadeia de caracteres
	 * @param label r�tulo a ser testado
	 * @return True se for aceit�vel, false caso contr�rio
	 */
	private boolean numberIsAcceptable(String string) {
		if (string.length() < 4) {
			return false;
		}

		if (!java.util.regex.Pattern.matches(NUMBER_REGEX, string)) {
			return false;
		}
		return true;
	}

	public void removeAllCommitments() {
		this.commitmentmanager = new CommitmentManager();
		
	}

	public void removeAllOperations(String accountID) {
		this.getAccount(accountID).removeAllOperations();
	}
}
